Tipos compósitos

Modelar una partícula:

Yo estaba esperando


In [1]:
x = 1
v = 2
m = 0.5


Out[1]:
0.5

In [2]:
dt = 0.125
x += v*dt


Out[2]:
1.25

In [3]:
x2 = 2
v2 = 10


Out[3]:
10

In [5]:
N = 10  # numero de particulas


Out[5]:
10

In [7]:
x = zeros(N)
v = ones(N);

Muchos hicieron


In [8]:
particula = [1, 2, 0.5]


Out[8]:
3-element Array{Float64,1}:
 1.0
 2.0
 0.5

La posición es


In [9]:
particula[1]


Out[9]:
1.0

Para actualizar su posición:


In [11]:
particula[1] += dt*particula[2]
# equivalente a   particula[1] = particula[1] + dt*particula[2]


Out[11]:
1.5

¡Es illegible! Queremos nombres que reflejen la función de cada variable: x y v, o posy vel, o posicion y velocidad

Tipos compósitos

Es la solución: combinación de variables con nombres, adentro de una sola estructura.


In [12]:
type Particula
    x
    v
    m
end

Aquí estamos definiendo un nuevo tipo de datos, que se llama Particula, y especificamos las características / propiedades internas de lo que es una Particula -- la estructura de una Particula.

Define una plantilla de una cajita que contiene espacio para estas tres variables (x, v y m).

¡Todavía no hay ninguna partícula! Para crear un objeto de este tipo (Particula): Utilizo Particula como si fuera una función:


In [13]:
p = Particula(1., 2., 0.5)


Out[13]:
Particula(1.0,2.0,0.5)

In [14]:
p


Out[14]:
Particula(1.0,2.0,0.5)

In [15]:
p.x


Out[15]:
1.0

In [16]:
p.v


Out[16]:
2.0

In [17]:
p.m


Out[17]:
0.5

In [18]:
typeof(p)


Out[18]:
Particula (constructor with 1 method)

Funciones que actúan sobre objetos de este tipo

Quiero definir una función mover que acepte un objeto de tipo Particula:


In [19]:
function mover(p, dt)
    p.x + p.v*dt
end


Out[19]:
mover (generic function with 1 method)

In [20]:
p


Out[20]:
Particula(1.0,2.0,0.5)

In [21]:
mover(p, 0.1)


Out[21]:
1.2

In [22]:
p


Out[22]:
Particula(1.0,2.0,0.5)

¡No se movió! Sólo se calculó la posición nueva. Para mover realmente el objeto, tenemos que cambiar sus propiedades internas:


In [24]:
function mover!(p, dt)  
    # ! es una convención en Julia que dice que la función
    # *modifica* su argumento -- tiene un *efecto secundario* (side effect)
    p.x += p.v*dt
end


Out[24]:
mover! (generic function with 1 method)

In [25]:
mover!(p, 1)


Out[25]:
3.0

In [26]:
p


Out[26]:
Particula(3.0,2.0,0.5)

No llamé a la función mover_particula


In [27]:
mover!(1, 0.1)


type Int64 has no field x
while loading In[27], in expression starting on line 1

 in mover! at In[24]:4

In [33]:
workspace()  # borra todo lo que está definido

In [36]:
type Prueba
    x
    v
    David
end

In [32]:
p = Particula(1,2,3)  # borré la definición


Particula not defined
while loading In[32], in expression starting on line 1

In [34]:
function mover!(p, dt)  
    # ! es una convención en Julia que dice que la función
    # *modifica* su argumento -- tiene un *efecto secundario* (side effect)
    p.x += p.v*dt
end


Out[34]:
mover! (generic function with 1 method)

In [37]:
d = Prueba(1, 2, 3)


Out[37]:
Prueba(1,2,3)

In [38]:
mover!(d, 0.1)


Out[38]:
1.2

In [39]:
d


Out[39]:
Prueba(1.2,2,3)

Mi función como tal mueve cualquier cosa que tenga un x y v adentro. Si no quiero esto:


In [41]:
type Particula
    x::Float64  # sólo puede ser de tipo Float64 
    v
    m
end

In [43]:
function mover!(p::Particula, dt)  
    # sólo actúa sobre objetos de tipo Particula
    p.x += p.v*dt
end


Out[43]:
mover! (generic function with 2 methods)

In [44]:
p = Particula(1, 2, 0.5)


Out[44]:
Particula(1.0,2,0.5)

In [46]:
mover!(p, 0.1)


Out[46]:
1.2

In [47]:
p


Out[47]:
Particula(1.2,2,0.5)

In [48]:
methods(mover!)


Out[48]:
2 methods for generic function mover!:
  • mover!(p::Particula,dt) at In[43]:3
  • mover!(p,dt) at In[34]:4

In [49]:
mover!(p, dt) = println("¡HOLA!")


Out[49]:
mover! (generic function with 2 methods)

In [50]:
p


Out[50]:
Particula(1.2,2,0.5)

In [51]:
mover!(p, 0.1)


Out[51]:
1.4

In [52]:
p


Out[52]:
Particula(1.4,2,0.5)

In [53]:
mover!(d, 0.1)


¡HOLA!

In [54]:
mover!(i::Int) = println(i)


Out[54]:
mover! (generic function with 3 methods)

In [55]:
methods(mover!)


Out[55]:
3 methods for generic function mover!:
  • mover!(p::Particula,dt) at In[43]:3
  • mover!(i::Int64) at In[54]:1
  • mover!(p,dt) at In[49]:1

In [56]:
mover!(1)


1

Un Gas

Un gas consiste en muchas partículas.


In [58]:
workspace()

In [60]:
type Particula
    x
    v
    m
end

In [59]:
type Gas_Malo
    x::Vector{Float64}
    v::Vector{Float64}
    m::Vector{Float64}
end

Esto es una mala idea, ya que no vemos que un Gas consiste en un montón de Partículas.


In [61]:
type Gas
    N::Int
    particulas::Vector{Particula}
end

Julia automáticamente (por defecto) crea una función constructora (o un constructor): una función con el mismo nombre que el tipo, que toma los datos y crea un objeto nuevo de este tipo, con los datos adentro.


In [66]:
g = Gas(2, [Particula(1,2,3), Particula(4, 5, 6)] )


Out[66]:
Gas(2,[Particula(1,2,3),Particula(4,5,6)])

In [67]:
g.N


Out[67]:
2

In [68]:
g.particulas


Out[68]:
2-element Array{Particula,1}:
 Particula(1,2,3)
 Particula(4,5,6)

In [69]:
g2 = Gas(5, [Particula(1,2,3), Particula(4, 5, 6)] )


Out[69]:
Gas(5,[Particula(1,2,3),Particula(4,5,6)])

Aquí estamos violando un invariante: queremos que el número N siempre sea igual al número de partículas en la lista: yo creo un nuevo constructor (que utiliza el constructor que ya había):


In [71]:
function Gas(particulas::Vector{Particula})
    Gas(length(particulas), particulas)
end


Out[71]:
Gas (constructor with 3 methods)

In [72]:
methods(Gas)


Out[72]:
3 methods for generic function Gas:
  • Gas(N::Int64,particulas::Array{Particula,1})
  • Gas(particulas::Array{Particula,1}) at In[71]:2
  • Gas(N,particulas)

In [73]:
g2 = Gas([Particula(1,2,3), Particula(4, 5, 6)])


Out[73]:
Gas(2,[Particula(1,2,3),Particula(4,5,6)])

Si quiero hacer una lista vacía de partículas e irla ampliando:


In [74]:
particulas = Particula[]


Out[74]:
0-element Array{Particula,1}

In [75]:
l = []


Out[75]:
0-element Array{None,1}

NB: En Julia 0.4, l=[] hace un arreglo de Any, ya no de None.


In [76]:
[3,4]


Out[76]:
2-element Array{Int64,1}:
 3
 4

In [83]:
v = Int[3,4]


Out[83]:
2-element Array{Int64,1}:
 3
 4

In [84]:
push!(v, 7)


Out[84]:
3-element Array{Int64,1}:
 3
 4
 7

In [85]:
push!(v, "David")


`convert` has no method matching convert(::Type{Int64}, ::ASCIIString)
while loading In[85], in expression starting on line 1

 in push! at array.jl:460

In [78]:
Float64[3,4]


Out[78]:
2-element Array{Float64,1}:
 3.0
 4.0

In [79]:
String[3,4]


`convert` has no method matching convert(::Type{String}, ::Int64)
while loading In[79], in expression starting on line 1

 in getindex at array.jl:121

In [80]:
w = Any[3,4]


Out[80]:
2-element Array{Any,1}:
 3
 4

In [82]:
push!(w, "David")


Out[82]:
3-element Array{Any,1}:
 3       
 4       
  "David"

In [86]:
particulas = Particula[]


Out[86]:
0-element Array{Particula,1}

In [87]:
push!(particulas, Particula(1, 2, 3))


Out[87]:
1-element Array{Particula,1}:
 Particula(1,2,3)

In [88]:
methods(mover!)


mover! not defined
while loading In[88], in expression starting on line 1

In [89]:
function mover!(g::Gas)
    # algo
end


Out[89]:
mover! (generic function with 1 method)

¡Tarea!

Haz un gas ideal:

  • Partículas en 2D (x y v que son vectores con 2 entradas)
  • Hacer una función que genere al azar una partícula
  • Hacer una función que agregue una partícula al Gas (cuidar que el número de partículas se actualice)
  • Hacer una función que genere un gas
  • Mover el gas: actualizar todas las partículas y hacer algo en las fronteras (de preferencia, colisiones elásticas con vectores)
  • [Para empezar: colisiones inelásticas: si se sale, se queda donde está; y/o condiciones periódicas: si se sale, se regresa del otro lado]

Pregunta 6 del notebook 6

Objetos que representan pares $(u(x_0), u'(x_0))$ para una función $u: \mathbb{R} \to \mathbb{R}$


In [116]:
workspace()

In [117]:
type ValorDeriv
    val
    der
end

In [118]:
a = ValorDeriv(1, 2)
b = ValorDeriv(3, 4)


Out[118]:
ValorDeriv(3,4)

In [119]:
a + b


`+` has no method matching +(::ValorDeriv, ::ValorDeriv)
while loading In[119], in expression starting on line 1

In [120]:
+(a::ValorDeriv, b::ValorDeriv) = ValorDeriv(a.val+b.val, a.der+b.der)


Out[120]:
+ (generic function with 120 methods)

In [121]:
a + b


Out[121]:
ValorDeriv(4,6)

a y b representan funciones $a(x)$ y $b(x)$ en algún lugar $x_0$ (implícito -- no viene representado explícitamente): a representa

$\bar{a} = (a(x_0), a'(x_0))$

a + b representa $((a+b)(x_0), (a+b)'(x_0))$


In [122]:
*(z::Number, a::ValorDeriv) = ValorDeriv(z*a.val, z*a.der)


Out[122]:
* (generic function with 118 methods)

In [123]:
*(a::ValorDeriv, b::ValorDeriv) = 
ValorDeriv(a.val*b.val, a.val*b.der + a.der*b.val)


Out[123]:
* (generic function with 119 methods)

El lugar en el cual evalúo las funciones queda codificado en x:


In [124]:
x = ValorDeriv(2, 1)  # el lugar es el x_0=2


Out[124]:
ValorDeriv(2,1)

In [125]:
p(x) = x*x + 2*x


Out[125]:
p (generic function with 1 method)

In [126]:
p(1)


Out[126]:
3

In [105]:
p(1.5)


Out[105]:
5.25

In [108]:
p(x)


Out[108]:
ValorDeriv(8,6)

Este resultado contiene $(p(x_0), p'(x_0))$ --es decir, ¡¡se calculó automáticamente la derivada!!


In [127]:
import Base.sin

In [135]:
sin(a::ValorDeriv) = ValorDeriv(sin(a.val), cos(a.val)*a.der)


Out[135]:
sin (generic function with 12 methods)

In [129]:
f(x) = sin(x)


Out[129]:
f (generic function with 1 method)

In [130]:
f(x)


Out[130]:
ValorDeriv(0.9092974268256817,0.4161468365471424)

In [137]:
g(x) = sin(x*x + 3x)


Out[137]:
g (generic function with 1 method)

In [138]:
g(x)


Out[138]:
ValorDeriv(-0.5440211108893698,-5.873500703535167)

In [133]:
sin(2*2)


Out[133]:
-0.7568024953079282

In [134]:
cos(2*2) * 2*2


Out[134]:
-2.6145744834544478

In [139]:
h(x) = sin(sin(x*x + 3x))


Out[139]:
h (generic function with 1 method)

In [140]:
h(x)


Out[140]:
ValorDeriv(-0.5175807674647733,-5.025568985012155)

In [142]:
import Base.show

In [145]:
show(io::IO, a::ValorDeriv) = print(io::IO, "Función con valor $(a.val) y 
derivada $(a.der)")


Out[145]:
show (generic function with 92 methods)

In [146]:
h(x)


Out[146]:
Función con valor -0.5175807674647733 y 
derivada -5.025568985012155

In [147]:
show(io::IO, a::ValorDeriv) = print(io::IO, "[$(a.val), $(a.der)]")


Out[147]:
show (generic function with 92 methods)

In [148]:
h(x)


Out[148]:
[-0.5175807674647733, -5.025568985012155]

In [150]:
z = h(x)
@sprintf "%.5f" z.val


Out[150]:
"-0.51758"

In [151]:
show(io::IO, a::ValorDeriv) = print(io::IO, "[$(@sprintf "%.5f" a.val), $(a.der)]")


Out[151]:
show (generic function with 92 methods)

In [152]:
h(x)


Out[152]:
[-0.51758, -5.025568985012155]

In [ ]:
"0.1"